package toy.keli.edic.data.model

import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.collection.arraySetOf
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.viewModelScope
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.common.internal.ImageUtils
import toy.keli.edic.data.Db
import toy.keli.edic.data.local.entity.ArticleEntity
import toy.keli.edic.data.local.entity.DictionaryEntity
import toy.keli.edic.data.parse.Image2Text
import toy.keli.edic.domain.model.WordModel
import toy.keli.edic.ui.bookmark.data.VirtualCommonItem
import toy.keli.edic.ui.main.extend.date
import toy.keli.edic.util.ActivityRun
import toy.keli.edic.util.FileUtil
import toy.keli.edic.util.MediaUtil
import toy.keli.edic.util.media.Camera
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import toy.keli.edic.data.config.BookConf
import toy.keli.edic.ui.main.components.Confirm.Companion.msg
import toy.keli.edic.ui.main.extend.bgRun
import toy.keli.edic.util.ActivityRun.Companion.msg
import toy.keli.edic.util.utilImage
import java.io.File
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import javax.inject.Inject

@HiltViewModel
class Article @Inject constructor() : Model() , BookItemMethods  {
    companion object {
        val dao = Db.user.article
        val self= Article()
        fun add(value: String, name: String, cid:Int=0,onAfter: Article.(ArticleEntity)->Unit={}) {
            val t = System.currentTimeMillis()
            self.save(ArticleEntity(value, name, cid,t,t),onAfter)
        }

        fun getByCid(
            cid: Int,
            callbak:(List<ArticleEntity>)->Unit={}
        ): MutableStateFlow<List<ArticleEntity>> {
            val aList = MutableStateFlow(listOf<ArticleEntity>())
            self.searchByCid(cid){
                aList.value = it
                callbak(it)
            }
            return aList
        }

        fun getContent(id: Int, onGet: (ct:String) -> Unit) {
            self.exec{
                val a = dao.find(id)
                onGet(a.content)
            }
        }

        fun delete(id: Int) {
//            self.exec{
//                dao.delete(id = id)
//            }
            self.delete(id)
        }

        fun finds(aids: List<Int>, hasContent: Boolean=false,f: (a: Array<ArticleEntity>) -> Unit) {
            val query = self.query.whereIn("id",aids)
            if(!hasContent) query.select("name,cid,uptime,intime,id,'' as content")
            self.launch{
                val a = dao.select(query.build())
                f(a)
            }
        }

        fun update(art : ArticleEntity) {
              self.save(art)
        }

        fun findByCids(cids: List<Int>, hasContent: Boolean, f: (a: Array<ArticleEntity>) -> Unit) {
            val query = self.query.whereIn("cid",cids)
            if(!hasContent) query.select("name,cid,uptime,intime,id,'' as content")
            val queryObj = query.build()
            self.launch{
                val a = dao.select(queryObj)
                f(a)
            }
        }

        fun grep(s: String,q:Query.()->Unit={}): Array<ArticleEntity> {
            val query = self.query.like("content",s)
            query.select("name,cid,uptime,intime,id,'' as content")
            q.invoke(query)
            val queryObj = query.build()
            return dao.select(queryObj)
        }

        /**
         * 通过单词获取对象
         */
        fun getWords(words: List<String>): List<WordModel> =runBlocking {
            val rt =Db.dictionary.dictionaryDao.search(words.map { it.trim() })
            rt.map{it.toWordModel()}
        }
        fun getWords(words: List<String>,f:(a:List<WordModel>) -> Unit) {
            self.launch {
                val sHave = arraySetOf<String>()
                val a = Db.dictionary.dictionaryDao.search(words.map{it.trim()}).map {
                    sHave.add(it.word)
                    it.toWordModel()
                }
                val rt = arrayListOf<WordModel>()
                rt.addAll(a)
                words.subtract(sHave).forEach{
                    rt.add(WordModel(null,it,"",0,0,"",""))
                }
                f(rt)
            }
        }

        fun addFile(data: String, f: String) {
            dao.addFile(data,f)
        }

        fun isEmpty(): Boolean {
            return  dao.select(self.query.limit(1).build()).size ==0
        }

        fun findByName(name: String): ArticleEntity? {
            return  self.waitLaunch({
                val q = self.query.where("name",name).limit(1)
                dao.select(q.build()).first()
            })
        }
        fun find(id: Int): ArticleEntity? {
            return  self.waitLaunch({
                dao.findOne(id)
            })
        }

        /**
         * 添加通过相机扫描
         */
        fun addByScan(onReadOver:ArticleEntity.(textToRead:List<String>)->Unit){
            val scanCat = Category.findByName("scan")
            val REQUEST_CODE = 100;
            if (ContextCompat.checkSelfPermission(
                    ActivityRun.context,
                    "android.permission.WRITE_EXTERNAL_STORAGE"
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                return ActivityCompat.requestPermissions(
                    ActivityRun.getAct(),
                    arrayOf("android.permission.WRITE_EXTERNAL_STORAGE"),
                    REQUEST_CODE
                )
            }
            var sid =if(scanCat==null){
                Category.add("scan",0)
            }else{
                scanCat.id
            }
            addByScan(sid,onReadOver)
        }
        fun addByScan(pid:Int,onReadOver:ArticleEntity.(textToRead:List<String>)->Unit){
            Camera.getPhoto{ img->
                val name  = "scan_"+date("MMddHHmm")
                add("parseing...",name, pid){
                    addIco(img,it.id)
                    bgRun {
                        Image2Text.convert(img) { textToRead->
                            it.content = textToRead.joinToString("\n")
                            update(it)
                            onReadOver.invoke(it,textToRead);
                        }
                    }
                }
            }
        }

        /**
         * 添加通过相册选择
         */
        fun addByImage(pid:Int,file:Uri,onReadOver:(textToRead:List<String>)->Unit){
            val img = InputImage.fromBitmap(MediaUtil.loadImageFromUri(ActivityRun.context,file)!!,0)
            val name = File(file.path).name+"_"+date("MMddHHmm")
            add("parseing...", "image_" + name, pid) {
                addIco(img, it.id)
                bgRun {
                    Image2Text.convert(img) { textToRead ->
                        it.content = textToRead.joinToString("\n")
                        update(it)
                    }
                }
            }
            onReadOver(listOf())
        }
        // 添加图片选择
        fun addByImage(pid:Int=0,onReadOver:(textToRead:List<String>)->Unit){
            MediaUtil.getPhoto{ img,file->
                Image2Text.convert(img) { textToRead ->
                    add(textToRead.joinToString("\n"), file.name, pid) {
                        addIco(img, it.id)
                    }
                    onReadOver(textToRead)
                }
            }
        }

        fun getArticleWords(name:String,function: (List<String>) -> Unit) {
            self.launch {
                val q = self.query.where("name",name).limit(1)
                dao.select(q.build()).firstOrNull()?.let {
                    function.invoke(it.findWords())
                }
            }
        }
        var tops =  mapOf<String,String>()
        //获取topn的数据列表
        fun getTop(id:Int,hasBefor:Boolean = false):List<String>{
            val name = "Top"+id
            if(tops.size==0) {
                val countDownLatch = CountDownLatch(1)
                self.launch {
                    val q = self.query.where("name","频率统计top4k.list").limit(1)
                    dao.select(q.build()).firstOrNull()?.let {
                        tops = it.content.split("\n").filter{it.trim().length >0 }.chunked(2).associate { it[0] to it[1] }
                        countDownLatch.countDown()
                    }
                }
                countDownLatch.await(500, TimeUnit.MILLISECONDS)
            }
            if(hasBefor){
                var i=-1
                val all = arrayListOf<String>()
                while(i++<=id){
                    all.addAll(getTop(i))
                }
                return all
            }
            val rt:List<String> = tops.get(name)?.split(",")?: listOf<String>()
            return rt
        }

        fun eachAll(run:ArticleEntity.(data:String)->Unit){
            self.launch {
                val q = self.query.select("0 as uptime,0 as intime,id,0 as cid,'' as name,'' as content").build()
                dao.select(q).forEach {
                    val p = dao.find(it.id)
                    run.invoke(p,p.content)
                }
            }
        }
    }

    open lateinit var entity: ArticleEntity
    constructor(id:Int,onGet: Article.(ArticleEntity) -> Unit) : this() {
        exec {
            entity =dao.find(id)
            onGet.invoke(this, entity)
        }
    }
    fun <T>waitLaunch(run: suspend CoroutineScope.() -> T?,ws:Long = 500) : T{
        val countDownLatch = CountDownLatch(1)
        var rt:T? = null
        viewModelScope.launch(Dispatchers.IO) {
             rt = run()
            countDownLatch.countDown()
        }
        countDownLatch.await(ws, TimeUnit.MILLISECONDS)
        return  rt as T
    }
    private fun launch(block: suspend CoroutineScope.() -> Unit) {
        viewModelScope.launch(Dispatchers.IO) {
            block()
        }
    }

    private fun delete(id: Int) {
        viewModelScope.launch(Dispatchers.IO) {
            dao.delete(id)
        }
    }

    private fun save(articleEntity: ArticleEntity,  onAfter: Article.(ae:ArticleEntity) -> Unit={}) {
        viewModelScope.launch(Dispatchers.IO) {
            try{
                if(articleEntity.id==0) {
                        articleEntity.id = dao.add(articleEntity).toInt()
                }else{
                    dao.update(articleEntity)
                }
            }catch (e:Exception){
                msg(e.message.toString());
                return@launch
            }
            onAfter.invoke(self,articleEntity)
        }
    }

    private fun searchByCid(cid: Int, onGet: (List<ArticleEntity>) -> Unit)  {
        viewModelScope.launch(Dispatchers.IO) {
            onGet(dao.search(cid=cid))
        }
    }

    fun hasIco(): Boolean {
        return hasIco(entity.id)
    }

    fun getIco(): Bitmap? {
        return getIco(entity.id)
    }
}